#include "scrambled_symbols.h"
#include "buffer.hxx"
#include "priv/blowfishstreamimpl.hxx"
#include "priv/mycomputer.hxx"

#define OPENSSL_NO_SHA512    1
#include <openssl/sha.h>

using namespace std;
using namespace MyComputer;

BlowfishStreamImpl::BlowfishStreamImpl()
  : _keysource(0),_key(0), _keysize(0), _buf(0), _bufsize(0), 
    _read_index(0), _write_index(0), _fixed_buf_size(false), 
    _readcache(0), _readcacheindex((tUInt32)-1), _cachedreadblockindex((tUInt32) -1),
    _writecache(0), _writecacheindex(0),_cachedwriteblockindex((tUInt32) -1),
    _ivec(0), _dirty(false)
{
  _readcache=new tByte[8];
  _writecache=new tByte[8];
  _ivec=new tByte[8];
}

BlowfishStreamImpl::BlowfishStreamImpl(tByte *buffer, tUInt32 buffersize)
    : _keysource(0),_key(0), _keysize(0), _buf((tByte *)buffer), _bufsize(buffersize), 
   _read_index(0), _write_index(0), _fixed_buf_size(true),_readcache(0), 
      _readcacheindex((tUInt32) -1), _cachedreadblockindex((tUInt32) -1), _writecache(0), 
    _writecacheindex(0),_cachedwriteblockindex((tUInt32) -1) ,
      _ivec(0), _dirty(false)
{
  _readcache=new tByte[8];
  _writecache=new tByte[8];
  _ivec=new tByte[8];
}

BlowfishStreamImpl::~BlowfishStreamImpl()
{
  Clear();

  if (_key)
    {
      delete _key;
      _key=0;
    }

  if (_keysource)
    {
      free (_keysource);
      _keysource=0;
    }

  if (_readcache)
    {
      delete [] _readcache;
      _readcache=0;
    }

  if (_writecache)
    {
      delete [] _writecache;
      _writecache=0;
    }

  if (_ivec)
    {
      delete [] _ivec;
      _ivec=0;
    }
}

tByte *BlowfishStreamImpl::Content()
{
  if (_dirty)
    {
      PadAndFinalize();
    }

  return _buf;
}

void BlowfishStreamImpl::SetBuffer(tByte *buffer, tUInt32 buffersize)
{
  _buf=buffer;
  _bufsize=buffersize;
  _fixed_buf_size=true;
}


void BlowfishStreamImpl::SetKey(const tByte *key, tUInt32 keysize)
{
  tMACAddressList maclist;
  tHardDiskList hdlist;

  Buffer *buff;
  tUInt32 count;

  buff=new Buffer();

  // Creating a buffer with hardisk and network card information ...
  count=GetHardDisks(hdlist);

  if (count!=(tUInt32) -1)
    {
      for (tHardDiskList::const_iterator it=hdlist.begin(); it!=hdlist.end(); it++)
	{
	  (*buff)<<*it;
	}
    }

  count=GetMACAddresses(maclist);

  if (count)
    {
      for (tMACAddressList::const_iterator it=maclist.begin(); it!=maclist.end(); it++)
	{
	  (*buff)<<*it;
	}
    }

  (*buff)<<GetComputerId();

  // Then appending the given key to the buffer.
  for (tUInt32 i=0; i<keysize; i++)
    {
      (*buff)<<key[i];
    }

  // Then computing the SHA1 sum of the previous buffer and appending
  // the result to the current key. So each time this function is called,
  // the key size increase by 20 bytes (explaining why we realloc with
  // 20 additional bytes).
  if (_keysource)
    {
      _keysource=(tByte *) realloc(_keysource,_keysize+20);
    }
  else
    {
      _keysource=(tByte *) malloc(20);
    }
  SHA1(buff->Content(),buff->WriteIndex(),_keysource+_keysize);
  _keysize+=20;

  delete buff;
}

void BlowfishStreamImpl::FinalizeKey()
{
  tByte *buf;
  _key=new BF_KEY();
  buf=new tByte[20];

  // Initializing vector (IV) is filled with value depending on 
  // values obtained from the key source itself.
  SHA1(_keysource,_keysize,buf);
  BF_set_key(_key,_keysize,_keysource);

  // Clearing source key
  memset(_keysource,0,_keysize);
  free(_keysource);
  _keysource=0;

  // We use the following operations in order to make reverse engineering
  // more difficult. Maybe we can improve it by using other kind of operations.
  _ivec[0]=buf[19]^buf[10]^buf[7]^buf[11]^buf[4]^0xAA;
  _ivec[1]=buf[18]^buf[6]^buf[10]^buf[17]|0xAA;
  _ivec[2]=buf[16]^buf[3]^buf[18]^buf[5]^buf[9]^buf[13];
  _ivec[3]=buf[0]^buf[15]^buf[13]^buf[16]^buf[4];
  _ivec[4]=buf[14]^buf[2]^buf[1]^buf[8]^buf[19];
  _ivec[5]=buf[0]^buf[19]^buf[12]^buf[8]|0x7C;
  _ivec[6]=buf[2]^buf[0]^buf[17]|buf[9]^buf[6];
  _ivec[7]=buf[10]^buf[0]^buf[19]|buf[5]^buf[12]^buf[11];

  memset(buf,0,20);
  delete [] buf;
}

void BlowfishStreamImpl::Append(const string &text)
{
  tUInt32 i;
  tUInt32 size;

  size=text.size();

  if (!Grow(size+1))
    {
      // The new string size cannot fit into the non expandable
      // buffer. We have to ignore request.
      return;
    }
  
  for (i=0; i<size; i++)
    {
      PushToWriteCache(text[i]);
    }

  PushToWriteCache('\0');

}

void BlowfishStreamImpl::Append(tUInt64 value)
{
  tByte *p;
  tUInt32 i;

  if (!Grow(sizeof(value)))
    {
      // The new value size cannot fit into the non expandable
      // buffer. We have to ignore request.
      return;
    }
  
  p=(tByte *) &value;

  for (i=0; i<sizeof(value); i++, p++)
    {
      PushToWriteCache(*p);
    }
}

void BlowfishStreamImpl::Append(tUInt32 value)
{
  tByte *p;
  tUInt32 i;

  if (!Grow(sizeof(value)))
    {
      // The new value size cannot fit into the non expandable
      // buffer. We have to ignore request.
      return;
    }
  
  p=(tByte *) &value;

  for (i=0; i<sizeof(value); i++, p++)
    {
      PushToWriteCache(*p);
    }

}

void BlowfishStreamImpl::Append(tUInt16 value)
{
  tByte *p;
  tUInt32 i;

  if (!Grow(sizeof(value)))
    {
      // The new value size cannot fit into the non expandable
      // buffer. We have to ignore request.
      return;
    }
  
  p=(tByte *) &value;

  for (i=0; i<sizeof(value); i++, p++)
    {
      PushToWriteCache(*p);
    }
}

void BlowfishStreamImpl::Append(tUInt8 value)
{
  tByte *p;
  tUInt32 i;

  if (!Grow(sizeof(value)))
    {
      // The new value size cannot fit into the non expandable
      // buffer. We have to ignore request.
      return;
    }
  
  p=(tByte *) &value;

  for (i=0; i<sizeof(value); i++, p++)
    {
      PushToWriteCache(*p);
    }
}

const char *BlowfishStreamImpl::ScanString()
{
  tUInt32 block_index;
  tUInt32 byte_index;
  char *res;
  char *p;

  if (_read_index>=_bufsize)
    {
      return (const char *) 0;
    }

  // We already know where the start of the string is.
  res=(char *) &_buf[_read_index];
  p=res;

  // Now we just need to find the end of the string. We have
  // to find the next '\0' character. So we have to decipher 
  // all blocks until encoutering a block with '\0'.
  block_index=_read_index/8;
  byte_index=_read_index%8;

  while (true)
    {
      if (BFSS_SuccessfullyLoaded==LoadReadCache(block_index))
	{
	  DecryptReadCache(block_index);
	  SyncCacheWithBuf(block_index);
	}

      if ((*p=='\0') || (_read_index>=_bufsize))
	{
	  break;
	}
      
      p++;
      _read_index++;
      byte_index++;
      if (byte_index==8)
	{
	  block_index++;
	  byte_index=0;
	}
    } // while (true)

  if (_read_index>=_bufsize)
    {
      return (const char *) 0;
    }

  // Here we have found the end of the string.
  _read_index++;

  return res;
}

tBool BlowfishStreamImpl::ScanInteger(tUInt64 *value)
{
  tUInt32 block_index_start;
  tUInt32 block_index_end;
  tUInt64 *p;

  if (_read_index+sizeof(tUInt64)>_bufsize) return false;

  // We need to decipher the appropriate blocks.
  // At most, we need to decipher two blocks. To
  // know which block to decipher, we will compute
  // the block index at the beginning of the value
  // and the end of the value.

  block_index_start=_read_index/8;

  if (BFSS_SuccessfullyLoaded==LoadReadCache(block_index_start))
    {
      DecryptReadCache(block_index_start);
      SyncCacheWithBuf(block_index_start);
    }

  p=(tUInt64 *) &_buf[_read_index];

  _read_index+=sizeof(tUInt64);

  block_index_end=(_read_index-1)/8;

  //
  if (block_index_end!=block_index_start)
    {
      if (BFSS_SuccessfullyLoaded==LoadReadCache(block_index_end))
	{
	  DecryptReadCache(block_index_end);
	  SyncCacheWithBuf(block_index_end);
	}
    }

  *value=*p;

  return true;
}

tBool BlowfishStreamImpl::ScanInteger(tUInt32 *value)
{
  tUInt32 block_index_start;
  tUInt32 block_index_end;
  tUInt32 *p;

  if (_read_index+sizeof(tUInt32)>_bufsize) return false;

  // We need to decipher the appropriate blocks.
  // At most, we need to decipher two blocks. To
  // know which block to decipher, we will compute
  // the block index at the beginning of the value
  // and the end of the value.

  block_index_start=_read_index/8;

  if (BFSS_SuccessfullyLoaded==LoadReadCache(block_index_start))
    {
      DecryptReadCache(block_index_start);
      SyncCacheWithBuf(block_index_start);
    }

  p=(tUInt32 *) &_buf[_read_index];

  _read_index+=sizeof(tUInt32);

  block_index_end=(_read_index-1)/8;

  //
  if (block_index_end!=block_index_start)
    {
      if (BFSS_SuccessfullyLoaded==LoadReadCache(block_index_end))
	{
	  DecryptReadCache(block_index_end);
	  SyncCacheWithBuf(block_index_end);
	}
    }

  *value=*p;

  return true;
}

tBool BlowfishStreamImpl::ScanInteger(tUInt16 *value)
{
  tUInt32 block_index_start;
  tUInt32 block_index_end;
  tUInt16 *p;

  if (_read_index+sizeof(tUInt16)>_bufsize) return false;

  // We need to decipher the appropriate blocks.
  // At most, we need to decipher two blocks. To
  // know which block to decipher, we will compute
  // the block index at the beginning of the value
  // and the end of the value.

  block_index_start=_read_index/8;

  if (BFSS_SuccessfullyLoaded==LoadReadCache(block_index_start))
    {
      DecryptReadCache(block_index_start);
      SyncCacheWithBuf(block_index_start);
    }

  p=(tUInt16 *) &_buf[_read_index];

  _read_index+=sizeof(tUInt16);

  block_index_end=(_read_index-1)/8;

  //
  if (block_index_end!=block_index_start)
    {
      if (BFSS_SuccessfullyLoaded==LoadReadCache(block_index_end))
	{
	  DecryptReadCache(block_index_end);
	  SyncCacheWithBuf(block_index_end);
	}
    }

  *value=*p;

  return true;
}

tBool BlowfishStreamImpl::ScanInteger(tUInt8 *value)
{
  tUInt32 block_index_start;
  tUInt8 *p;

  if (_read_index+sizeof(tUInt8)>_bufsize) return false;

  // We need to decipher the appropriate blocks.
  // At most, we need to decipher two blocks. To
  // know which block to decipher, we will compute
  // the block index at the beginning of the value
  // and the end of the value.

  block_index_start=_read_index/8;

  if (BFSS_SuccessfullyLoaded==LoadReadCache(block_index_start))
    {
      DecryptReadCache(block_index_start);
      SyncCacheWithBuf(block_index_start);
    }

  p=(tUInt8 *) &_buf[_read_index];

  _read_index+=sizeof(tUInt8);

  *value=*p;

  return true;

}

void BlowfishStreamImpl::Seek(tUInt32 position)
{
  if (position>_bufsize) return;

  _read_index=position;
  _write_index=position;

}

void BlowfishStreamImpl::Clear()
{
  if (_ivec)
    {
      memset(_ivec,0,8);
    }

  if (_readcache)
    {
      memset(_readcache,0,8);
    }

  if (_writecache)
    {
      memset(_writecache,0,8);
    }

  if ( (!_fixed_buf_size) && (_buf!=0))
    {
      memset(_buf,0,_bufsize);
      free(_buf);
      _bufsize=0;
      _buf=0;
    }
  
  _read_index=0;
  _write_index=0;

}

tUInt32 BlowfishStreamImpl::Size() const
{
  return _bufsize;
}

tUInt32 BlowfishStreamImpl::ReadIndex() const
{
  return _read_index;
}

tUInt32 BlowfishStreamImpl::WriteIndex() const
{
  return _write_index;
}

void BlowfishStreamImpl::PadAndFinalize()
{
  // Push 0 until cache is synchronized with buffer (becomes clean).
  while (_dirty)
    {
      PushToWriteCache(0);
    }
}

BlowfishStreamImpl::tBFSCacheStatus BlowfishStreamImpl::LoadReadCache(tUInt32 block_index) const
{
  tUInt32 byteindex;
  tUInt32 i;

  byteindex=block_index*8;

  if (byteindex==_readcacheindex)
    {
      return BFSS_AlreadyLoaded;
    }

  if (byteindex>=_bufsize)
    {
      return BFSS_Error;
    }

  _readcacheindex=byteindex;

  for (i=0; i<8; i++, byteindex++)
    {
      // This should never happen but who knows!
      if (byteindex>=_bufsize)
	{
	  return BFSS_Error;
	}

      _readcache[i]=_buf[byteindex];
    }

  return BFSS_SuccessfullyLoaded;

}

void BlowfishStreamImpl::DecryptReadCache(tUInt32 block_index) const
{
  // TODO: Make the key depend on the block index.
  BF_cbc_encrypt(_readcache,_readcache, 8, _key, &_ivec[0], BF_DECRYPT);
}

void BlowfishStreamImpl::EncryptWriteCache(tUInt32 block_index)
{
  // TODO: Make the key depend on the block index.
  BF_cbc_encrypt(_writecache,_writecache, 8, _key, &_ivec[0], BF_ENCRYPT);
}

bool BlowfishStreamImpl::Grow(tUInt32 newobjectsize)
{
  tUInt32 block_count;
  tUInt32 new_size;

 // Computing the new number of required block to contain
  // the new length of buffer.
  // 
  // +7 to reserve enough space for the rest of the eventually 
  // newly started block.
  block_count=(_write_index+newobjectsize+7)/8;

  // Total size should always be an even amount of 8 bytes.
  new_size=block_count*8;

  // Do we need to grow?
  if (new_size>_bufsize)
    {
      // Yes!
      if (_fixed_buf_size)
	{
	  // But here we don't have the right to modify the buffer 
	  // size.
	  return false;
	}

      _buf=(tByte *) realloc(_buf,new_size); 
      _bufsize=new_size;
    }

  return true;
}
